#include "ZhicheEntity.h"
#include "core/CMath.h"
#include "core/CUtil.h"
#include "Camera.h"
#include "Cache.h"
#include "DiskCache.h"
#include "Global.h"
#include "GLTFParser.h"
#ifdef __QT__
	#include "platform/qt/QDynQml.h"
#endif

static const float ZhicheSSAA = 2.0;

bool ZhicheEntity::s_hasRenderedDone = false;

ZhicheEntity* ZhicheEntity::New() {
	if (GLTFParser::isZhicheParsedDone())
		return new ZhicheEntity;
	return nullptr;
}

ZhicheEntity::ZhicheEntity(): ModelEntity(ModelType::Zhiche, 123456) {
	Camera::get()->connectCamChangeSignal(this);
	LogI << "ZhicheEntity is creating...";

	#ifdef __QT__
		loadAsyncDoors();
	#endif
}

void ZhicheEntity::slotCameraChange() { m_isCameraChanged = true; }

void ZhicheEntity::updateState() {
	const auto& arg = Cache::zhicheArg();
	setPosition(arg.pos.toVec3(Config::Y_Model));
	setRotationY(arg.angle);
}

ZhicheEntity::~ZhicheEntity() {
	delete m_fbo;
	s_hasRenderedDone = false;
}

/**@note called per frame(MyRenderThread)*/
void ZhicheEntity::renderAsTexture(FBO* fboToWrite) {
	if (!Cache::isReceivingData())
		return;

	if (!s_hasRenderedDone || m_isCameraChanged || Cache::isZhicheDirty() || m_isDoorChanged) {
		if (!m_fbo)
			m_fbo = GLState::createFBO(ZhicheSSAA, ZhicheTexUnit, ZhicheSampler2DName);

		GLState::bindAndClear(m_fbo, ZhicheSSAA);

		const auto& scalerFunc = DataFilter::get()->get2DScalerFunc();
		auto scale = scalerFunc(Cache::zhicheArg().pos.z);
		this->setScale(scale);

		this->render();

		GLState::waitToFinish();
		m_isCameraChanged = false;

		// for project like KuanTi, in the production environment, app startup using cache datas, do not do useless things(do not update zhiche Png)
		if (!s_hasRenderedDone) {// cache zhiche-fbo as image to local disk
			const auto& ZhicheBGPath = Config::View3D_ZhichePngPath;
			if (!CUtil::fileExists(ZhicheBGPath) || DiskCache::getVerCode() != Config::Version_Code) {
				CUtil::mkdirIfNotExists(ZhicheBGPath);
				const auto w = W3D * Config::View3D_ZhichePngOccupationWRate;
				const auto h = H3D * Config::View3D_ZhichePngOccupationHRate;
				GLState::captureFBO(ZhicheBGPath, true, (W3D - w) / 2, H3D - h, w, h, ZhicheSSAA);
				GLState::waitToFinish();

				LogI << "zhiche Image(png) and Info has (re)cached!";
			}
			DiskCache::updateVersion();
		}
	}
	m_isDoorChanged = false;

	GLState::bind(fboToWrite);

	if (!m_shader.isValid()) {
		m_shader.install(Config::Shader_SceneTexture);
		CVector<Vec2> vertices;
		vertices << Vec2(-1, 1) << Vec2(-1, -1) << Vec2(1, 1) << Vec2(1, -1);
		m_shader.prepareVABO(GLSLVarName::Vertex, vertices);
	}
	m_shader.use();
	m_shader.uniformRef().setSampler2D(Sampler2DArg(m_fbo->texture, m_fbo->texUnit, m_fbo->sampler2DName));
	GLState::enableBlend();
	GLState::disableCullFace();
	m_shader.draw(PrimitiveType::TRIANGLE_STRIP);

	if (!s_hasRenderedDone) {
		s_hasRenderedDone = true;
		#ifdef __QT__
			if (IsEnv_Linux)// KT project, in embed device, avoid a bug: a flash of zhiche occur when startup
				CUtil::delay([&] { QDynQml::get()->setZhichePngVisible(false); }, 500);
			else
				QDynQml::get()->setZhichePngVisible(false);// notify qml to hide zhiche PNG
		#endif
	}
}

void ZhicheEntity::handleDoor(HDir side, Switch status) {//bool isLeft, bool open
	if (!m_isDoorsLoadedDone) {// doors not loaded yet, do nothing
		m_isDoorChanged = false;
		return;
	}

	auto& lastStatus = m_doorStatusMap.valueRef(side);
	if (lastStatus == status) {// no change, do nothing
		m_isDoorChanged = false;
		return;
	}
	lastStatus = status;
	m_isDoorChanged = true;

	bool left = side == HDir::LEFT;
	bool open = status == Switch::ON;
	const auto& doorModels = left ? m_doorLeftModels : m_doorRightModels;
	const auto& doorMinZPoint = left ? m_doorLeftMinZPoint : m_doorRightMinZPoint;
	auto rotY = (left ? 1 : -1) * (open ? -C_PI/2 : 0);
	const CColor color = "LightCoral";
	for (int i = 0; i < doorModels.size(); i++) {
		auto door = doorModels[i];
		door->setLightEnabled(!open);// or reverse normal vector maybe
		const auto& meshIndexNamePair = door->meshProperty();
		if (meshIndexNamePair.value != "Mirror")
			door->setCullFaceEnabled(!open);
		door->setRotationY(rotY, doorMinZPoint);
		if (open)
			door->setMaterialColor(color.lighter(100 + i * 6));
		else
			door->setMaterialColor(GLTFParser::material(ModelType::Zhiche, meshIndexNamePair.key).baseColor);// reset to default glTF color
	}
}

void ZhicheEntity::loadAsyncDoors() {
	CUtil::doInBgThread([&] {
		m_doorLeftMinZPoint.z = m_doorRightMinZPoint.z = std::numeric_limits<float>::max();

		const auto& children = ModelEntity::children();
		for (const auto& child : children) {
			const auto& meshIndexName = child->meshProperty();
			int meshIndex = meshIndexName.key;
			const auto& meshName = meshIndexName.value;
			if (meshName.contains("HB_Door_Left")) {// all left door parts
				m_doorLeftModels << child;
				if (meshName.contains("HB_Door_Left_Core")) {// left door main part
					auto& vertices = GLTFParser::vertices(ModelType::Zhiche, meshIndex);
					for (auto& vertex : vertices) {
						if (vertex.z < m_doorLeftMinZPoint.z)
							m_doorLeftMinZPoint = vertex;
					}
				}
			} else if (meshName.contains("HB_Door_Right")) {// all right door parts
				m_doorRightModels << child;
				if (meshName.contains("HB_Door_Right_Core")) {// right door main part
					auto& vertices = GLTFParser::vertices(ModelType::Zhiche, meshIndex);
					for (auto& vertex : vertices) {
						if (vertex.z < m_doorRightMinZPoint.z)
							m_doorRightMinZPoint = vertex;
					}
				}
			}
			if (meshName.contains("Mirror"))// some special parts: mirrors, should be treated standalone/specially, otherwise, bad visual artifacts
				child->setCullFaceEnabled(false);
		}

		m_doorStatusMap.insert(HDir::LEFT, Switch::OFF);
		m_doorStatusMap.insert(HDir::RIGHT, Switch::OFF);

		m_isDoorsLoadedDone = true;
	});
}